package lapsnake.model.dnd;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JPanel;

import owg.util.awt.VerticalLayout;
import lapsnake.model.LapNode;
import lapsnake.model.NodeRegister;
import lapsnake.ui.BlendComposite;

public class ActionNodePanel<T extends LapNode> extends NodePanel<T>
{
	private static final long serialVersionUID = 11L;	
	public static final int MAJOR_ICON_DIM = 16;
	public static final int MINOR_ICON_DIM = 8;
	public static final int DOWNSET = 4;
	
	private GridBagConstraints cenHor;
	private GridBagConstraints highHor;
	
	/**Containers for child lists found in the node's ChildFields*/
	private ArrayList<FieldPanel> fieldPanels;
	/**Independent row panels.*/
	private ArrayList<JPanel> rows;
	private JPanel currentRow;
	/**Indices of row breaks in the fieldPanels list. The indices are sorted in increasing order.*/
	private ArrayList<Integer> rowBreakIndices;
	
	public volatile boolean renderPreview = false;
	
	public ActionNodePanel(StmtPanel rootPanel, T myNode)
	{
		super(rootPanel, myNode);
		fieldPanels = new ArrayList<>();
		rows = new ArrayList<>();
		rowBreakIndices = new ArrayList<>();
		setLayout(new VerticalLayout(-MINOR_ICON_DIM, VerticalLayout.LEFT));
		setBorder(BorderFactory.createEmptyBorder());
		setOpaque(false);
		setBackground(NodeRegister.getNodeColor(myNode.getClass()));
		line(0);
	}
	
	private void line(int lineNumber)
	{
		if(lineNumber != 0)
			rowBreakIndices.add(fieldPanels.size());
		
		currentRow = new JPanel(new GridBagLayout());
		currentRow.setOpaque(false);
		rows.add(currentRow);
		add(currentRow);
		
		cenHor = new GridBagConstraints();
		cenHor.gridwidth = 1;
		cenHor.gridheight = 1;
		cenHor.weightx = 0;
		cenHor.weighty = 0;
		cenHor.anchor = GridBagConstraints.NORTHWEST;
		cenHor.fill = GridBagConstraints.NONE;
		cenHor.gridy = 0;
		cenHor.gridx = 0;
		cenHor.ipadx = 0;
		cenHor.ipady = 0;
		cenHor.insets = new Insets(0, 0, 0, 0);
		
		Dimension d;
		d = new Dimension(MINOR_ICON_DIM+MAJOR_ICON_DIM, MINOR_ICON_DIM);
		add(new Box.Filler(d,d,d));

		cenHor.weighty = 1;
		
		//cenHor.gridx = 0;
		//cenHor.fill = GridBagConstraints.VERTICAL;
		//cenHor.gridheight = 2;
		//JPanel rowHeightFiller = new JPanel();
		//rowHeightFiller.setOpaque(false);
		//rowHeightFillers.add(rowHeightFiller);
		//add(rowHeightFiller);
		//cenHor.gridheight = 1;
		//cenHor.fill = GridBagConstraints.NONE;

		cenHor.gridx = 0;
		cenHor.gridy = 1;
		
		d = new Dimension(MINOR_ICON_DIM+MAJOR_ICON_DIM, MAJOR_ICON_DIM+DOWNSET+2);
		add(new Box.Filler(d,d,d));
		
		cenHor.insets = new Insets(DOWNSET, 0, 2, 5);
		cenHor.weightx = 1;
		highHor = new GridBagConstraints(cenHor.gridx, cenHor.gridy, cenHor.gridwidth, cenHor.gridheight, 
			cenHor.weightx, cenHor.weighty, cenHor.anchor, cenHor.fill, cenHor.insets, cenHor.ipadx, cenHor.ipady);
		highHor.gridheight = 2;
		highHor.weighty = 0;
		highHor.gridy = 0;
	}

	public void addLow(Component comp)
	{
		add(comp, cenHor);
	}
	
	public void addHigh(Component comp)
	{
		add(comp, highHor);
	}
	
	@Override
	protected void addImpl(Component comp, Object constraints, int index)
	{
		if(comp == currentRow)
		{
			super.addImpl(comp, null, index);
			return;
		}
		
		if(constraints == null)
			constraints = cenHor;
		assert constraints == cenHor || constraints == highHor;
		assert cenHor != highHor;
		
		if(comp != null)
			currentRow.add(comp, constraints);
		
		cenHor.gridx ++;
		if (highHor != null)
			highHor.gridx ++;
	}
	
	protected boolean showUpperJig()
	{
		synchronized(ActionNodePanel.class)
		{
			return (node.parentField.isList() && node.parentField.get(0) != node) || renderPreview;
		}
	}
	protected boolean showLowerJig()
	{
		synchronized(ActionNodePanel.class)
		{
			return node.parentField.isList() || renderPreview;
		}
	}
	protected boolean showLeftJig()
	{
		return true;
	}
	
	@Override
	protected void paintComponent(Graphics g)
	{
		JPanel row = rows.get(0);
		int colIndex = 0;
		int rowIndex = 0;
		
		int x0 = 0;
		int y0 = row.getY();
		int h = row.getHeight();
		
		boolean single = fieldPanels.size() == 0;
		
		for(int i = 0; i<fieldPanels.size(); i++)
		{
			if(rowIndex < rowBreakIndices.size() && i >= rowBreakIndices.get(rowIndex))
			{
				colIndex = 0;
				rowIndex++;
				row = rows.get(rowIndex);
				x0 = 0;
				y0 = row.getY();
				h = row.getHeight();
			}
			FieldPanel next = fieldPanels.get(i);
			int w = next.getX()+MINOR_ICON_DIM - x0;

			g.translate(x0, y0);
			drawJig(g, getBackground(), w, h, 
				(showLeftJig() && i == 0)?node.getClass():null, 
				next, 
				(showUpperJig() && i == 0)?node.getClass():null, 
				(showLowerJig() && colIndex == 0 && rowIndex == rowBreakIndices.size())?node.parentField.getType():null);
			g.translate(-x0, -y0);
			x0 += w+next.getWidth()-MINOR_ICON_DIM-MINOR_ICON_DIM;
			
			colIndex++;
		}
		int w = getWidth()-x0;
		if(!single)
			w = Math.min(w, MINOR_ICON_DIM+5);
		g.translate(x0, y0);
		drawJig(g, getBackground(), w, h, 
			(showLeftJig() && single)?node.getClass():null, null, 
			(showUpperJig() && single)?node.getClass():null, (showLowerJig() && single)?node.parentField.getType():null);
		g.translate(-x0, -y0);
		
		Image micro = NodeRegister.getImage(node.getClass(), "Micro");
		if(micro != null)
		{
			g.drawImage(micro, MINOR_ICON_DIM, (showUpperJig())?MINOR_ICON_DIM : MINOR_ICON_DIM+2, null);
		}
	}
	
	private static void drawJig(Graphics g, Color col, int width, int height, 
		Class<? extends LapNode> leftPlug, FieldPanel rightSockets,
		Class<? extends LapNode> upperPlug, Class<? extends LapNode> lowerSocket)
	{
		Graphics2D g2 = (Graphics2D)g;
		g2.setComposite(new BlendComposite(col));
		if(leftPlug!=null)
			g.drawImage(NodeRegister.getImage(leftPlug, "PlugV"), 1, MINOR_ICON_DIM, null);
		ArrayList<Integer> rightYs = null;
		if(rightSockets!=null)
		{
			rightYs = new ArrayList<>();
			rightYs.add(MINOR_ICON_DIM);
			if(rightSockets.nodePanels.isEmpty())
			{
				int y = MINOR_ICON_DIM+DOWNSET;
				rightYs.add(y);
				g.drawImage(NodeRegister.getImage(rightSockets.getFieldType(), "SocketV"), width-MINOR_ICON_DIM, y, null);
			}
			else
			{
				for(NodePanel<?> cp : rightSockets.nodePanels)
				{
					int y = cp.getY()+MINOR_ICON_DIM+DOWNSET;
					rightYs.add(y);
					g.drawImage(NodeRegister.getImage(cp.node.parentField.getType(), "SocketV"), width-MINOR_ICON_DIM, y, null);
				}
			}
			rightYs.add(height);
		}
		if(upperPlug!=null)
			g.drawImage(NodeRegister.getImage(upperPlug, "PlugH"), MINOR_ICON_DIM, 0, null);
		if(lowerSocket!=null)
			g.drawImage(NodeRegister.getImage(lowerSocket, "SocketH"), MINOR_ICON_DIM, height-MINOR_ICON_DIM, null);
		
		g2.setComposite(AlphaComposite.SrcOver);
		g.setColor(col);
		//Left fill
		g.fillRect(MINOR_ICON_DIM+1, MINOR_ICON_DIM, 
			Math.max(0, Math.min(width-MINOR_ICON_DIM, MAJOR_ICON_DIM+MINOR_ICON_DIM))-MINOR_ICON_DIM-1, height-MINOR_ICON_DIM*((lowerSocket!=null)?2:1));
		
		if(rightSockets != null)
		{
			//Center fill
			if(width > MINOR_ICON_DIM*2+MAJOR_ICON_DIM)
				g.fillRect(MINOR_ICON_DIM+MAJOR_ICON_DIM, MINOR_ICON_DIM, 
					width-(MINOR_ICON_DIM*2+MAJOR_ICON_DIM), height-MINOR_ICON_DIM);
			//Right fill
			int rx = Math.max(MINOR_ICON_DIM, width-(MINOR_ICON_DIM));
			int currY = rightYs.get(0);
			for(int i = 0; i<rightYs.size()-1; i++)
			{
				int nextY = rightYs.get(i+1);
				g.fillRect(rx, currY, width-rx, nextY-currY);
				currY = nextY+MAJOR_ICON_DIM;
			}
		}
		else//Center & right fill
		{
			int rx = Math.min(MINOR_ICON_DIM+MAJOR_ICON_DIM, Math.max(width-MINOR_ICON_DIM, MINOR_ICON_DIM));
			g.fillRect(rx, MINOR_ICON_DIM, 
				width-rx, height-MINOR_ICON_DIM);
		}
		
		
		g.setColor(Color.BLACK);
		//Left line
		g.drawLine(MINOR_ICON_DIM, MINOR_ICON_DIM+((leftPlug!=null)?MAJOR_ICON_DIM:0), MINOR_ICON_DIM, height-1);
		//Right line
		if(rightSockets == null)
			g.drawLine(width-1, MINOR_ICON_DIM, width-1, height-1);
		else
		{
			int currY = rightYs.get(0);
			for(int i = 0; i<rightYs.size()-1; i++)
			{
				int nextY = rightYs.get(i+1);
				g.drawLine(width-1, currY, width-1, nextY-1);
				currY = nextY+MAJOR_ICON_DIM;
			}
		}
		
		//Upper line
		g.drawLine(MINOR_ICON_DIM+((upperPlug!=null)?MAJOR_ICON_DIM:0), MINOR_ICON_DIM, width-1, MINOR_ICON_DIM);
		//Lower line
		g.drawLine(MINOR_ICON_DIM+((lowerSocket!=null)?MAJOR_ICON_DIM:0), height-1, width-1, height-1);
	}

	@Override
	public Socket getSocketAt(Point p)
	{
		for (FieldPanel f : fieldPanels)
		{
			Socket sock = f.getSocketAt(p);
			if(sock != null)
				return sock;
		}
		Point myPoint = getAbsoluteLocation();
		if (p.x < myPoint.x || p.y < myPoint.y)
			return null;
		if (p.x >= myPoint.x+getWidth()+MINOR_ICON_DIM)
			return null;
		int middleY = myPoint.y + MINOR_ICON_DIM + (getHeight()-MINOR_ICON_DIM)/2;
		
		if(p.y < middleY)
		{
			if(showUpperJig() || showLeftJig())
				return new Socket(rootPanel, this, node.parentField, node.parentField.indexOf(node), showUpperJig(), new Point(0, 0));
		}
		else
		{
			if(showLowerJig())
				return new Socket(rootPanel, this, node.parentField, node.parentField.indexOf(node)+1, true, new Point(0, getHeight()-MINOR_ICON_DIM));
			else if(showLeftJig())
				return new Socket(rootPanel, this, node.parentField, node.parentField.indexOf(node), false, new Point(0, 0));
		}
		return null;
	}

	@Override
	public Point getAbsoluteLocation()
	{
		Point loc = getLocationOnScreen();
		Point root = rootPanel.getLocationOnScreen();
		loc.translate(-root.x, -root.y);
		return loc;
	}
	
	@Override
	public void getChildPanels(ArrayList<NodePanel<?>> builder)
	{
		for(FieldPanel f : fieldPanels)
		{
			for(NodePanel<?> panel : f.nodePanels)
			{
				builder.add(panel);
				panel.getChildPanels(builder); 
			}
		}
	}

	public void registerFieldPanel(FieldPanel fieldPanel)
	{
		fieldPanels.add(fieldPanel);
	}
	
	@Override
	public String toString()
	{
		return "ANP: "+node;
	}

	public void newLine()
	{
		line(this.rows.size());
	}
}
